////////////////////////////////////////////////////////////////////////////////
// Includes
////////////////////////////////////////////////////////////////////////////////

#include "SCProgram.hpp"

#include <iomanip>
#include <fstream>
#include <exception>
#include "IOFileException.hpp"
#include "TKMacros.h"

////////////////////////////////////////////////////////////////////////////////
// Namespaces
////////////////////////////////////////////////////////////////////////////////

namespace SC {

////////////////////////////////////////////////////////////////////////////////
// Implementation
////////////////////////////////////////////////////////////////////////////////

using namespace std;
using namespace Types;
using namespace Exceptions;

SCProgram::SCProgram()
{
	this->Clear();
}

SCProgram::SCProgram(const string & filename) {
	this->ReadByteCode(filename);
}

const vector<unsigned int> SCProgram::GetSuperSystems(const unsigned int idx) const {
	vector<unsigned int> super_sys;
	for (unsigned int i=0; i<this->sys_inst.size(); ++i) {
		for (unsigned int j=0; j<this->sys_inst[i].GetThird().size(); ++j) {
			if (this->sys_inst[i].GetThird()[j] == idx) {
				super_sys.push_back(i);
				break;
			}
		}
	}
	return super_sys;
}

// Number of codes per line
#define CPL 3

void SCProgram::Print(ostream & ostream) const {
	ostream << "========== Encoding ==========" << endl;
	ostream << "Compression codes : "<< this->codes.size() << endl;
	for (unsigned int i=0; i<this->codes.size(); i+=CPL) {
		ostream << "  " << this->codes[i].GetFirst() << " " << this->codes[i].GetSecond();
		for (unsigned int j=i+1; (j<i+CPL) && (j<this->codes.size()); ++j)
			ostream << "     " << this->codes[j].GetFirst() << " " << this->codes[j].GetSecond();
		ostream << endl;
	}
	ostream << "External modules  : " << this->func_modules.size() << endl;
	for (unsigned int i=0; i<this->func_modules.size(); ++i)
		ostream << "  " << this->func_modules[i] << endl;
	ostream << "Word length       : " << this->word_length << endl;
	ostream << "Function offset   : [" << this->func_offset.GetBegin() << ":" << this->func_offset.GetEnd() << "]" << endl;	
	ostream << "========== Functions ==========" << endl;
	ostream << "Functions : "<< this->functions.size() << endl;
	for (unsigned int i=0; i<this->functions.size(); ++i)
		ostream << "  " << this->functions[i].GetFirst().substr(this->func_offset.GetBegin(),this->func_offset.GetEnd()-this->func_offset.GetBegin()+1) << " " << this->functions[i].GetSecond() << endl;
	ostream << "========== Systems ==========" << endl;
	ostream << "Definitions : " << this->sys_decl.size() << endl;
	for (unsigned int i=0; i<this->sys_decl.size(); ++i)
		ostream << "  [" << i << "]  " << this->sys_decl[i].GetSecond() << " : " << this->sys_decl[i].GetFirst() << endl;
	ostream << "========== Program ==========" << endl;
	ostream << "Instances : " << this->sys_inst.size() << endl;
	for (unsigned int i=0; i<this->sys_inst.size(); ++i) {
		ostream << "  " << this->sys_decl[this->sys_inst[i].GetFirst()].GetFirst() << " " << this->sys_inst[i].GetSecond() << endl;
		ostream << "    " << this->sys_inst[i].GetThird().size() << " Sub {";
		for (unsigned int j=0; j<this->sys_inst[i].GetThird().size(); ++j) {
			ostream << this->sys_inst[this->sys_inst[i].GetThird()[j]].GetSecond();
			if (j<this->sys_inst[i].GetThird().size()-1) ostream << ", ";
		}
		ostream << "}" << endl;
		vector<unsigned int> super_sys = this->GetSuperSystems(i);
		ostream << "    " << super_sys.size() << " Super {";
		for (unsigned int j=0; j<super_sys.size(); ++j) {
			ostream << this->sys_inst[super_sys[j]].GetSecond();
			if (j<super_sys.size()-1) ostream << ", ";
		}
		ostream << "}" << endl;
	}
}

void SCProgram::WriteByteCode(const string & filename) const {
	ofstream file(filename.c_str(),ios::out|ios::binary);
	if (!file.is_open())
		throw IOFileException("Cannot write to file \"" + filename + "\"");
	try {
		unsigned int size;
		// Word length
		file.write(reinterpret_cast <const char *>(&this->word_length), sizeof(unsigned int));
		// Func offset
		file.write(reinterpret_cast <const char *>(&this->func_offset.GetBegin()), sizeof(int));
		file.write(reinterpret_cast <const char *>(&this->func_offset.GetEnd()), sizeof(int));
		// System declarations
		size = this->sys_decl.size();
		file.write(reinterpret_cast <const char *>(&size), sizeof(unsigned int));
		for (unsigned int i=0; i<this->sys_decl.size(); ++i) {
			// Name
			size = this->sys_decl[i].GetFirst().size();
			file.write(reinterpret_cast <const char *>(&size), sizeof(unsigned int));
			file.write(reinterpret_cast <const char *>(this->sys_decl[i].GetFirst().c_str()), this->sys_decl[i].GetFirst().size());
			// Code
			size = this->sys_decl[i].GetSecond().size();
			file.write(reinterpret_cast <const char *>(&size), sizeof(unsigned int));
			file.write(reinterpret_cast <const char *>(this->sys_decl[i].GetSecond().c_str()), this->sys_decl[i].GetSecond().size());
		}
		// Instances declarations
		size = this->sys_inst.size();
		file.write(reinterpret_cast <const char *>(&size), sizeof(unsigned int));
		for (unsigned int i=0; i<this->sys_inst.size(); ++i) {
			// Index
			file.write(reinterpret_cast <const char *>(&this->sys_inst[i].GetFirst()), sizeof(unsigned int));
			// Name
			size = this->sys_inst[i].GetSecond().size();
			file.write(reinterpret_cast <const char *>(&size), sizeof(unsigned int));
			file.write(reinterpret_cast <const char *>(this->sys_inst[i].GetSecond().c_str()), this->sys_inst[i].GetSecond().size());
			// Sub-context indices
			size = this->sys_inst[i].GetThird().size();
			file.write(reinterpret_cast <const char *>(&size), sizeof(unsigned int));
			for (unsigned int j=0; j<this->sys_inst[i].GetThird().size(); ++j)
				file.write(reinterpret_cast <const char *>(&this->sys_inst[i].GetThird()[j]), sizeof(unsigned int));
		}
		// Codes
		size = this->codes.size();
		file.write(reinterpret_cast <const char *>(&size), sizeof(unsigned int));
		for (unsigned int i=0; i<this->codes.size(); ++i) {
			// Encoded char
			file.write(reinterpret_cast <const char *>(&this->codes[i].GetFirst()), sizeof(char));
			// Code string
			size = this->codes[i].GetSecond().size();
			file.write(reinterpret_cast <const char *>(&size), sizeof(unsigned int));
			file.write(reinterpret_cast <const char *>(this->codes[i].GetSecond().c_str()), this->codes[i].GetSecond().size());
		}
		// Functions
		size = this->functions.size();
		file.write(reinterpret_cast <const char *>(&size), sizeof(unsigned int));
		for (unsigned int i=0; i<this->functions.size(); ++i) {
			// Code
			size = this->functions[i].GetFirst().size();
			file.write(reinterpret_cast <const char *>(&size), sizeof(unsigned int));
			file.write(reinterpret_cast <const char *>(this->functions[i].GetFirst().c_str()), this->functions[i].GetFirst().size());
			// Name
			size = this->functions[i].GetSecond().size();
			file.write(reinterpret_cast <const char *>(&size), sizeof(unsigned int));
			file.write(reinterpret_cast <const char *>(this->functions[i].GetSecond().c_str()), this->functions[i].GetSecond().size());
		}
		// Function module
		size = this->func_modules.size();
		file.write(reinterpret_cast <const char *>(&size), sizeof(unsigned int));
		for (unsigned int i=0; i<this->func_modules.size(); ++i) {
			size = this->func_modules[i].size();
			file.write(reinterpret_cast <const char *>(&size), sizeof(unsigned int));
			file.write(reinterpret_cast <const char *>(this->func_modules[i].c_str()), this->func_modules[i].size());
		}
		// Close file
		file.close();
	}
	catch (exception & e) {
		file.close();
		throw;
	}
}

#define BUF_SIZE 4096

void SCProgram::ReadByteCode(const std::string & filename) {
	this->Clear();
	ifstream file(filename.c_str(),ios::in|ios::binary);
	if (!file.is_open())
		throw IOFileException("Cannot read file \"" + filename + "\"");
	this->fullpath = filename;
	if ((filename.size() > 4) && (filename[filename.size()-4] == '.'))
		this->name = filename.substr(0,filename.size()-4);
	else
		this->name = filename;
	int sep_idx = MAX((int)this->name.find_last_of('/'),(int)this->name.find_last_of('\\'));
	if (sep_idx != (int)string::npos) {
		this->path = this->name.substr(0,sep_idx+1);
		this->name = this->name.substr(sep_idx+1,this->name.size()-sep_idx);
	}
	else this->path = "";
	try {
		unsigned int size, size2;
		char buf[BUF_SIZE];
		// Word length
		file.read(reinterpret_cast <char *>(&this->word_length), sizeof(unsigned int));
		// Func offset
		file.read(reinterpret_cast <char *>(&this->func_offset.Begin()), sizeof(int));
		file.read(reinterpret_cast <char *>(&this->func_offset.End()), sizeof(int));
		// System declarations
		file.read(reinterpret_cast <char *>(&size), sizeof(unsigned int));
		for (unsigned int i=0; i<size; ++i) {
			Couple<string,string> decl;
			// Name
			file.read(reinterpret_cast <char *>(&size2), sizeof(unsigned int));
			file.read(buf,size2);
			decl.First() = string(buf,size2);
			// Code
			file.read(reinterpret_cast <char *>(&size2), sizeof(unsigned int));
			decl.Second() = readLongStringFromStream(file,buf,BUF_SIZE,size2);
			// Add
			this->sys_decl.push_back(decl);
		}
		// Instances declarations
		file.read(reinterpret_cast <char *>(&size), sizeof(unsigned int));
		for (unsigned int i=0; i<size; ++i) {
			Triple<unsigned int, string, vector<unsigned int> > inst;
			// Index
			file.read(reinterpret_cast <char *>(&inst.First()), sizeof(unsigned int));
			// Name
			file.read(reinterpret_cast <char *>(&size2), sizeof(unsigned int));
			file.read(buf,size2);
			inst.Second() = string(buf,size2);
			// Sub indices
			file.read(reinterpret_cast <char *>(&size2), sizeof(unsigned int));
			unsigned int idx;
			for (unsigned int j=0; j<size2; ++j) {
				file.read(reinterpret_cast <char *>(&idx), sizeof(unsigned int));
				inst.Third().push_back(idx);
			}
			// Add
			this->sys_inst.push_back(inst);
		}
		// Codes
		file.read(reinterpret_cast <char *>(&size), sizeof(unsigned int));
		for (unsigned int i=0; i<size; ++i) {
			Couple<char,string> code;
			// Encoded char
			file.read(reinterpret_cast <char *>(&code.First()), sizeof(char));
			// Code string
			file.read(reinterpret_cast <char *>(&size2), sizeof(unsigned int));
			file.read(buf,size2);
			code.Second() = string(buf,size2);
			// Add
			this->codes.push_back(code);
		}
		// Functions
		file.read(reinterpret_cast <char *>(&size), sizeof(unsigned int));
		for (unsigned int i=0; i<size; ++i) {
			Couple<string,string> func;
			// Code
			file.read(reinterpret_cast <char *>(&size2), sizeof(unsigned int));
			file.read(buf,size2);
			func.First() = string(buf,size2);
			// Name
			file.read(reinterpret_cast <char *>(&size2), sizeof(unsigned int));
			file.read(buf,size2);
			func.Second() = string(buf,size2);
			// Add
			this->functions.push_back(func);
		}
		// Function modules
		file.read(reinterpret_cast <char *>(&size), sizeof(unsigned int));
		for (unsigned int i=0; i<size; ++i) {
			file.read(reinterpret_cast <char *>(&size2), sizeof(unsigned int));
			file.read(buf,size2);
			this->func_modules.push_back(string(buf,size2));
		}
		// Close file
		file.close();
	}
	catch (exception & e) {
		file.close();
		throw IOFileException("Error while reading program file \"" + filename + "\"");
	}
}

void SCProgram::Clear() {
	this->name = "";
	this->word_length = 0;
	this->func_offset.Begin() = -1;
	this->func_offset.End() = -1;
	this->sys_decl.clear();
	this->sys_inst.clear();
	this->codes.clear();
	this->functions.clear();
	this->func_modules.clear();
}

const string SCProgram::readLongStringFromStream(
	istream & stream,
	char * buf,
	const unsigned int buf_size,
	const unsigned int data_size
) {
	string result;
	unsigned int data_left = data_size, read_size;
	while (data_left > 0) {
		read_size = (data_left>buf_size) ? buf_size : data_left;
		stream.read(buf,read_size);
		result += string(buf,read_size);
		data_left -= read_size;
	}
	return result;
}

////////////////////////////////////////////////////////////////////////////////
// Closing Namespaces
////////////////////////////////////////////////////////////////////////////////

}

////////////////////////////////////////////////////////////////////////////////
